package com.example.myapplication

import org.junit.Test
import org.objectweb.asm.*
import org.objectweb.asm.commons.AdviceAdapter
import org.objectweb.asm.commons.Method
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.util.Locale

class ASMUnitTest {

    @Test
    fun test(){
        val path = "src/test/java/com/example/myapplication/InjectTest.class"
        val fileInputStream = FileInputStream(File(path))

        val reader = ClassReader(fileInputStream)
        val writer = ClassWriter(ClassWriter.COMPUTE_FRAMES)
        reader.accept(MyClassVisitor(Opcodes.ASM7, writer), ClassReader.EXPAND_FRAMES)
        val bytes = writer.toByteArray()

//        val fos =  FileOutputStream(path)
        val fos =  FileOutputStream("src/test/java/com/example/myapplication/InjectTest1.class")
        fos.write(bytes)
        fos.flush()
        fos.close()
    }

}

class MyClassVisitor(api: Int, classVisitor: ClassVisitor): ClassVisitor(api, classVisitor) {

    override fun visitMethod(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        exceptions: Array<out String>?
    ): MethodVisitor {
        println("$name")
        val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions)
        return MyMethodVisitor(api, methodVisitor, access, name, descriptor)
    }
}

class MyMethodVisitor(api: Int, classVisitor: MethodVisitor, access: Int,
                      name: String?,
                      descriptor: String?,): AdviceAdapter(api, classVisitor,access, name, descriptor) {

    var s: Int = 0
    var inject = false
    override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
        println("visitAnnotation() ${name}: ${descriptor}")
        inject = descriptor == "Lcom/example/myapplication/AsmAnnotation;"
        return super.visitAnnotation(descriptor, visible)
    }

    override fun onMethodEnter() {
        super.onMethodEnter()
        if (!inject){
            return
        }
        //Long l = System.currentTimeMillis()
        invokeStatic(Type.getType("Ljava/lang/System"), Method("currentTimeMillis", "()J"))
        s = newLocal(Type.LONG_TYPE)
        storeLocal(s)
    }

    override fun onMethodExit(opcode: Int) {
        super.onMethodExit(opcode)
        //Long e = System.currentTimeMillis()
//        System.out.println("excute" + (e-l) + "ms")

        if (!inject){
            return
        }

        invokeStatic(Type.getType("Ljava/lang/System"), Method("currentTimeMillis", "()J"))
        val e = newLocal(Type.LONG_TYPE)
        storeLocal(e)

        /**
         *  20: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         *       23: new           #8                  // class java/lang/StringBuilder
         *       26: dup
         *       27: invokespecial #9                  // Method java/lang/StringBuilder."<init>":()V
         *       30: ldc           #10                 // String excute
         *       32: invokevirtual #11                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
         *       35: aload_2
         *       36: invokevirtual #12                 // Method java/lang/Long.longValue:()J
         *       39: aload_1
         *       40: invokevirtual #12                 // Method java/lang/Long.longValue:()J
         *       43: lsub
         *       44: invokevirtual #13                 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
         *       47: ldc           #14                 // String ms
         *       49: invokevirtual #11                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
         *       52: invokevirtual #15                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
         *       55: invokevirtual #16                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         *       58: return
         * */

        getStatic(Type.getType("Ljava/lang/System"),  "out", Type.getType("Ljava/io/PrintStream;"))
        newInstance(Type.getType("Ljava/lang/StringBuilder"))
        dup()
        invokeConstructor(Type.getType("Ljava/lang/StringBuilder"), Method("<init>", "()V"))
        visitLdcInsn("excute")

        invokeVirtual(Type.getType("Ljava/lang/StringBuilder"), Method("append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;"))
        loadLocal(e)
        loadLocal(s)
        math(SUB, Type.LONG_TYPE)
        invokeVirtual(Type.getType("Ljava/lang/StringBuilder"), Method("append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;"))

        visitLdcInsn("ms")
        invokeVirtual(Type.getType("Ljava/lang/StringBuilder"), Method("append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;"))
        invokeVirtual(Type.getType("Ljava/lang/StringBuilder"), Method("toString", "()Ljava/lang/String;"))
        invokeVirtual(Type.getType("Ljava/io/PrintStream"), Method("println", "(Ljava/lang/String;)V"))

    }
}
